﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace PayloadTracker.OSM
{
    public delegate void TileInvalidatedDelegate(Tile t);
    /// <summary>
    /// Class representing one Tile retrevied from http://www.openstreetmap.org/
    /// </summary>
    public class Tile
    {
        private System.Drawing.Image m_ErrorImage = null;
        private System.Drawing.Image m_BlankImage = null;

        public static readonly int TILE_SIZE = 256;
        public event TileInvalidatedDelegate TileInvalidated;

        System.Net.WebClient m_WebClient = null;
        System.Drawing.Image m_Image;
        Coordinate m_UL;
        Coordinate m_LR;
        int m_Zoom, m_TileX, m_TileY;
        string m_CacheFile;

        /// <summary>
        /// Image of the tile.
        /// </summary>
        public System.Drawing.Image Image
        {
            get { return m_Image; }
            set { m_Image = value; }
        }
        /// <summary>
        /// Coordinate of the Upper Left corner
        /// </summary>
        public PayloadTracker.Coordinate UL
        {
            get { return m_UL; }
            set { m_UL = value; }
        }
        /// <summary>
        /// Coordinate of the Lower Right corner
        /// </summary>
        public PayloadTracker.Coordinate LR
        {
            get { return m_LR; }
            set { m_LR = value; }
        }
        /// <summary>
        /// Zoom level of the tile.
        /// </summary>
        public int Zoom
        {
            get { return m_Zoom; }
            set { m_Zoom = value; }
        }
        /// <summary>
        /// X-position of tile on the tile server.
        /// </summary>
        public int TileX
        {
            get { return m_TileX; }
            set { m_TileX = value; }
        }
        /// <summary>
        /// Y-position of the tile on the tile server.
        /// </summary>
        public int TileY
        {
            get { return m_TileY; }
            set { m_TileY = value; }
        }
        /// <summary>
        /// path to the file on disk where the tile is stored.
        /// </summary>
        public string CacheFile
        {
            get { return m_CacheFile; }
            set { m_CacheFile = value; }
        }
        /// <summary>
        /// center coordinate of the tile.
        /// </summary>
        public Coordinate Center
        {
            //TODO: Implement.
            get { return new Coordinate();  }
        }
        /// <summary>
        /// Unique tile ID.
        /// </summary>
        public string TileID 
        {
            get { return Zoom.ToString() + m_TileX.ToString() + m_TileY.ToString();  }
        }
        public Tile(Coordinate pos, int Zoom)
        {
            Initialize(Tile.Long2Tile(pos.Long, Zoom), Tile.Lat2Tile(pos.Lat, Zoom), Zoom);
        }
        public Tile(int X, int Y, int Zoom)
        {
            Initialize(X, Y, Zoom);
        }
        public Tile(int X, int Y, int Zoom, bool Async)
        {
            Initialize(X, Y, Zoom,Async);
        }
        public Tile()
        {

        }
        private void Initialize(int X, int Y, int Zoom)
        {
            Initialize(X, Y, Zoom, true);
        }
        private void Initialize(int X, int Y, int Zoom, bool Async)
        {
            m_WebClient = new System.Net.WebClient();
            m_WebClient.Headers.Add("user-agent", "PayloadTracker/" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString());
            m_WebClient.DownloadFileCompleted += new System.ComponentModel.AsyncCompletedEventHandler(m_WebClient_DownloadFileCompleted);
            Image = GetBlankImage();

            string tilepath = Path.Combine(Zoom.ToString(), Path.Combine(X.ToString(), Y.ToString() + ".png"));
            string cachepath = Settings.Default["TileCache"] as string;

            System.Drawing.Image tileimg = null;

            //try to load from file.
			string fullTilePath = Path.Combine(cachepath, tilepath);
            if (System.IO.File.Exists(fullTilePath))
            {
                try
                {
                    tileimg = System.Drawing.Image.FromFile(fullTilePath);
                }
                catch (System.Exception ex)
                {
                    Logger.LOG.Error("Error loading tile: " + fullTilePath + " due to: " + ex.ToString() + " Deleteing and attempting to download.");
                    System.IO.File.Delete(fullTilePath);
                    tileimg = null;
                }

            }
            if (tileimg == null)
            {
                tileimg = GetBlankImage();

                //check for cache path.
                if (!System.IO.Directory.Exists(Path.Combine(cachepath, Path.Combine(Zoom.ToString(), X.ToString()))))
                    System.IO.Directory.CreateDirectory(Path.Combine(cachepath, Path.Combine(Zoom.ToString(), X.ToString())));


                //Download the tile.
                string tileurl = (Settings.Default["TileServer"] as string) + tilepath;
                try
                {
                    Logger.LOG.WriteLine("Downloading Tile: " + tileurl);
                    if(Async)
                        m_WebClient.DownloadFileAsync(new System.Uri(tileurl), Path.Combine(cachepath, tilepath));
                    else
                    {
                        m_WebClient.DownloadFile(tileurl, Path.Combine(cachepath, tilepath));
                        Image = System.Drawing.Image.FromFile(Path.Combine(cachepath, tilepath));

                        TileInvalidatedDelegate del = TileInvalidated;
                        if (del != null)
                            del(this);
                    }
                }
                catch (System.Exception ex)
                {
                    Logger.LOG.Error("Error downloading tile: " + tileurl + " due to: " + ex.ToString());
                    tileimg = GetErrorImage();
                }
            }

            this.CacheFile = Path.Combine(cachepath, tilepath);
            this.Image = tileimg;
            this.UL = new Coordinate(Tile.Tile2Lat(Y, Zoom), Tile.Tile2Long(X, Zoom));
            this.LR = new Coordinate(Tile.Tile2Lat(Y + 1, Zoom), Tile.Tile2Long(X + 1, Zoom));
            this.Zoom = Zoom;
            this.TileX = X;
            this.TileY = Y;


        }

        void m_WebClient_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
        {
            try
            {
                Image = System.Drawing.Image.FromFile(CacheFile);
            }
            catch (System.Exception ex)
            {
                Logger.LOG.Error("Failed opening tile: " + CacheFile + " from cache deleting entry. "+ex.ToString());
                System.IO.File.Delete(CacheFile);
                Image = GetErrorImage();
            }
            
            
            TileInvalidatedDelegate del = TileInvalidated;
            if (del != null)
                del(this);
        }
        private System.Drawing.Image GetErrorImage()
        {
            if (m_ErrorImage == null)
            {
                var bmp = new System.Drawing.Bitmap(Tile.TILE_SIZE, Tile.TILE_SIZE);
                var gfx = System.Drawing.Graphics.FromImage(bmp);

                gfx.FillRectangle(System.Drawing.SystemBrushes.InactiveBorder, 0, 0, Tile.TILE_SIZE, Tile.TILE_SIZE);
                gfx.DrawString("Error", System.Drawing.SystemFonts.DefaultFont, System.Drawing.Brushes.Black, 0, 0);

                m_ErrorImage = bmp;
            }

            return m_ErrorImage;
        }
        private System.Drawing.Image GetBlankImage()
        {
            if (m_BlankImage == null)
            {

                var bmp = new System.Drawing.Bitmap(Tile.TILE_SIZE, Tile.TILE_SIZE);
                var gfx = System.Drawing.Graphics.FromImage(bmp);

                gfx.FillRectangle(System.Drawing.SystemBrushes.InactiveBorder, 0, 0, Tile.TILE_SIZE, Tile.TILE_SIZE);

                m_BlankImage = bmp;

            }

            return m_BlankImage;
        }

        public static System.Drawing.Point Coordinate2Tile(Coordinate c, int Zoom)
        {
            return new System.Drawing.Point(Long2Tile(c.Long,Zoom), Lat2Tile(c.Lat,Zoom));
        }
        public static int Long2Tile(double Long, int Zoom)
        {
            return ((int)Math.Floor((Long + 180.0f) / 360.0 * Math.Pow(2.0, Zoom)));
        }
        public static int Lat2Tile(double Lat, int Zoom)
        {
            return (int)(Math.Floor((1.0 - Math.Log( Math.Tan(Lat * Math.PI/180.0) + 1.0 / Math.Cos(Lat* Math.PI/180.0)) / Math.PI) / 2.0 * Math.Pow(2.0, Zoom))); 
        }
        public static double Tile2Long(Tile t)
        {
            return Tile2Lat(t.TileX, t.Zoom);
        }
        public static double Tile2Long(int x, int Zoom)
        {
            return x / Math.Pow(2.0, Zoom) * 360.0 - 180;
        }
        public static double Tile2Lat(Tile t)
        {
            return Tile2Lat(t.TileY, t.Zoom);
        }
        public static double Tile2Lat(int y, int Zoom)
        {
            double n = Math.PI - 2.0 * Math.PI * y / Math.Pow(2.0, Zoom);
            return 180.0 / Math.PI * Math.Atan(0.5 * (Math.Exp(n) - Math.Exp(-n)));
        }

        public static BoundingBox Tile2BoundingBox(Tile t)
        {
            return Tile2BoundingBox(t.TileX, t.TileY, t.Zoom);
        }
        public static BoundingBox Tile2BoundingBox(System.Drawing.Point p,int Zoom)
        {
            return Tile2BoundingBox(p.X, p.Y, Zoom);
        }
        public static BoundingBox Tile2BoundingBox(int x, int y, int Zoom)
        {
            return new BoundingBox(Tile2Lat(y, Zoom), Tile2Long(x + 1, Zoom), Tile2Lat(y + 1, Zoom), Tile2Long(x, Zoom));
        }
    }
}
